﻿using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Xml.Serialization;
using ZOIL.DataModel;

namespace ZOIL.Library.Components
{
    public class ZPortalShape : InformationObjectBase, ICustomUpdateable, INotifyObjectRemotelyCreated
    {
        #region ctor

        public ZPortalShape()
        {
            _isInitialized = false;
        }

        #endregion

        #region public properties

        #region Path

        private string _path;
        public string Path
        {
            get { return _path; }
            set
            {
                if (value == _path)
                {
                    return;
                }
                _path = value;
                RaisePropertyChanged("Path");
                OnObjectLocallyChanged();
            }
        }

        #endregion

        #region HitTestTypes

        //Types können nicht von serialisiert werden (da Type von .Net spezial behandelt -> besonderer Singleton)
        [Db4oIgnore]
        private Type[] _hitTestTypes;
        [XmlIgnore]
        public Type[] HitTestTypes
        {
            get { return _hitTestTypes; }
            set
            {
                if (Equals(value, _hitTestTypes))
                {
                    return;
                }
                _hitTestTypes = value;
                RaisePropertyChanged("HitTestTypes");
                RaisePropertyChanged("ShapeGeometry");
                OnObjectLocallyChanged();
            }
        }

        [XmlArray("HitTestTypes")]
        [XmlArrayItem("HitTestType")]
        public string[] HitTestTypesNames
        {
            get
            {
                return _hitTestTypes != null ? _hitTestTypes.Select(t => t.Namespace + "." + t.Name).ToArray() : null;
            }
            set
            {
                if (value == null)
                {
                    HitTestTypes = null;
                }
                else
                {
                    HitTestTypes = value.ToList().Select(name =>
                        {
                            Type t;
                            return TypeFinder.TryFindType(name, out t) ? t : null;
                        }).Where(t => t != null).ToArray();
                }
            }
        }
        #endregion

        #region ShapeGeometry
        [XmlIgnore]
        public Geometry ShapeGeometry
        {
            get
            {
                //we need to set the current culture to en-US because the parser needs to have the same culture as the toString() method used to save the path
                var oldCultureInfo = Thread.CurrentThread.CurrentCulture;
                var oldUICultureInfo = Thread.CurrentThread.CurrentUICulture;
                Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
                Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

                PathGeometry shapeGeometry =  Geometry.Parse(Path).GetOutlinedPathGeometry();

                //set the culture back to the previous
                Thread.CurrentThread.CurrentCulture = oldCultureInfo;
                Thread.CurrentThread.CurrentUICulture = oldUICultureInfo;

                return shapeGeometry;
            }
            set
            {
                //this is mostly called after a selection has been finished and a new PortalShape was initialized

                var shapeGeometry = value.GetOutlinedPathGeometry();
                Path = shapeGeometry.ToString(new CultureInfo("en-US"));
                RaisePropertyChanged("ShapeGeometry");
            }
        }

        #endregion

        #region SelectedElements

        [Db4oIgnore]
        [XmlIgnore]
        private ObservableCollection<object> _selectedElements;
        [XmlIgnore]
        public ObservableCollection<object> SelectedElements
        {
            get
            {
                if (_selectedElements == null)
                    SelectedElements = new ObservableCollection<object>();

                return _selectedElements;
            }
            set
            {
                _selectedElements = value;
                RaisePropertyChanged("SelectedElements");
                OnObjectLocallyChanged();
            }
        }
        #endregion

        #region IsInitialized
        private bool _isInitialized;

        public bool IsInitialized
        {
            get { return _isInitialized; }
            set
            {
                if (value.Equals(_isInitialized))
                {
                    return;
                }
                _isInitialized = value;
                RaisePropertyChanged("IsInitialized");
                OnObjectLocallyChanged();
            }
        }

        #endregion

        #region Bounds
        [XmlIgnore]
        public Rect Bounds
        {
            get { return ShapeGeometry.Bounds; }
        }

        #endregion

        #endregion

        #region public methods

        public void InvalidateShape()
        {
            ShapeGeometry = ShapeGeometry;
        }

        #endregion

        #region Remote Updated
        public override void OnRemoteUpdated()
        {
            base.OnRemoteUpdated();
            RaisePropertyChanged("Bounds");
            RaisePropertyChanged("HitTestTypes");
            RaisePropertyChanged("IsInitialized");
            RaisePropertyChanged("Path");
            RaisePropertyChanged("ShapeGeometry");
        }
        #endregion

        #region Implementation of ICustomUpdateable

        [XmlIgnore]
        [Db4oIgnore]
        private static readonly string[] _exculdedFields = new[] { LinksFieldName, "_path" };

        [XmlIgnore]
        [Db4oIgnore]
        private readonly object _onUpdateLock = new object();
        /// <summary>
        ///     Called when a new remote update is received.
        ///     If a class does not implement this interface all fields will be exchanged
        ///     If a class implementens this interface it MUST handle the exchange on its own!
        /// </summary>
        /// <param name="remotelyUpdatedObject"> Object with the changed.Can be casted into the implementing class (: this.GetType() == <see
        ///      cref="remotelyUpdatedObject" /> .GetType()). </param>
        public virtual void OnUpdate(object remotelyUpdatedObject)
        {
            lock (_onUpdateLock)
            {
                var u = (ZPortalShape)remotelyUpdatedObject;

                Debug.Assert(_exculdedFields.Contains(LinksFieldName));
                CustomUpdateLinks(u.Links);

                //Path austauschen
                var uPath = u.Path;
                var cPath = Path;
                if (!Equals(uPath, cPath))
                {
                    Interlocked.Exchange(ref _path, uPath);
                }

                //und rest normal austauschen
                this.ExchangeFieldsOnFirstLevel(remotelyUpdatedObject, _exculdedFields);
            }
        }

        #endregion

        #region Implementation of INotifyObjectRemotelyCreated

        [XmlIgnore]
        [Db4oIgnore]
        private bool _wasRemotelyCreated = false;
        [XmlIgnore]
        public bool WasRemotelyCreated
        {
            get { return _wasRemotelyCreated; }
        }
        public virtual void OnRemotelyCreated()
        {
            _wasRemotelyCreated = true;
        }

        #endregion
    }
}